/*
  Time-stamp: <2010-09-22 17:03:44 xinhaoyuan>

  File: ips.c

  This file provide kernel-level inter-process synchronizing principle.

  Two structure are implemented, mutex and semaphore.

*/

#define _EKOS_KERNEL_C_
#include <kernel/kernel.h>

/* = MUTEX ==================================================== */

/* Assume that the sizeof mutex is 6 */
static char __mutex_size[sizeof(struct mutex_t) == 6 ? 1 : -1];

const struct mutex_t
initalized_mutex = {0, ENV_NULL, ENV_NULL};

void
mutex_init(volatile struct mutex_t *mutex)
{
	 sl_init(&mutex->lock);
	 mutex->cur = ENV_NULL;
	 mutex->sleep = ENV_NULL;
}

int
mutex_lock(volatile struct mutex_t *mutex)
{
	 sl_lock(&mutex->lock);
	 
	 if (mutex->cur == ENV_NULL)
	 {
		  mutex->cur = cur_env;
		  sl_release(&mutex->lock);
		  
		  return 0;
	 }
	 else if (mutex->cur == cur_env) {
		  sl_release(&mutex->lock);

		  return 1;
	 }
	 else
	 {
#if 0
		  NOINTR_S;
		  cprintf("[mutex_lock] %d, mutex = %08x, cur = %d, sleep = %d\n", cur_env, va2pa(mutex), mutex->cur, mutex->sleep);
		  NOINTR_E;
#endif

		  if (mutex->sleep == ENV_NULL)
			   mutex->sleep = cur_env;
		  
		  sch_sleep(mutex->sleep, &mutex->lock);
		  call_yield();

		  return 0;
	 }
}

bool
mutex_trylock(volatile struct mutex_t *mutex)
{
	 bool result;
	 
	 sl_lock(&mutex->lock);

	 if (mutex->cur == ENV_NULL)
	 {
		  mutex->cur = cur_env;
		  result = true;
	 }
	 else result = false;
	 
	 sl_release(&mutex->lock);

	 return result;
}

void
mutex_unlock(volatile struct mutex_t *mutex)
{
	 sl_lock(&mutex->lock);

	 if (mutex->cur == cur_env)
	 {
		  uint16_t next = mutex->sleep;
			   
		  mutex->cur = next;

		  if (next != ENV_NULL)
		  {
#if 0
			   NOINTR_S;
			   cprintf("[mutex_unlock] %d, mutex = %08x, cur = %d, sleep = %d\n", cur_env, va2pa(mutex), mutex->cur, mutex->sleep);
			   NOINTR_E;
#endif

			   uint16_t next_sleep = envs[next].sched_info.next;
			   
			   if (next_sleep == next)
					next_sleep = ENV_NULL;
			   mutex->sleep = next_sleep;
			   
			   sch_wakeup(next, &mutex->lock);			   
		  }
		  else
		  {
#if 0
			   NOINTR_S;
			   cprintf("[mutex_unlock] %d, mutex = %08x\n", cur_env, va2pa(mutex));
			   NOINTR_E;
#endif
			   
			   sl_release(&mutex->lock);
		  }
	 }
	 else
	 {
		  sl_release(&mutex->lock);
	 }
}

/* = SEMAPHORE ================================================ */

const struct semaphore_t
initalized_semaphore = {0, 0, ENV_NULL};

static char __semaphore_size[sizeof(struct semaphore_t) == 6 ? 1 : -1];

void
semaphore_init(volatile struct semaphore_t *sem)
{
	 sl_init(&sem->lock);
	 sem->count = 0;
	 sem->sleep = ENV_NULL;
}

void
semaphore_wait(volatile struct semaphore_t *sem)
{
	 sl_lock(&sem->lock);

	 // cprintf("[semaphore_wait] %d, sleep\n", cur_env);

	 if (sem->count > 0) {
		  sem->count --;
		  sl_release(&sem->lock);
	 }
	 else 
	 {
		  if (sem->sleep == ENV_NULL)
			   sem->sleep = cur_env;
		  
		  sch_sleep(sem->sleep, &sem->lock);
		  call_yield();
	 }

	 // cprintf("[semaphore_wait] %d, wakeup\n", cur_env);
}

void
semaphore_notify(volatile struct semaphore_t *sem)
{
	 sl_lock(&sem->lock);

	 // cprintf("[semaphore_notify] %d, notify, count = %d, sleep = %d\n", cur_env, sem->count, sem->sleep);

	 if (sem->count == 0 && sem->sleep != ENV_NULL)
	 {
		  uint16_t next = sem->sleep;
		  uint16_t next_sleep = envs[next].sched_info.next;
			   
		  if (next_sleep == next)
			   next_sleep = ENV_NULL;
		  sem->sleep = next_sleep;
		  
		  sch_wakeup(next, &sem->lock);
		  // cprintf("[semaphore_notify] %d, wakeup %d\n", cur_env, next);
	 }
	 else
	 {
		  if (sem->count < MAX_SEM_COUNT)
			   ++sem->count;
		  sl_release(&sem->lock);
	 }
}

void
semaphore_clear(volatile struct semaphore_t *sem, int count)
{
	 sl_lock(&sem->lock);

	 if (sem->sleep != ENV_NULL) {
		  NOINTR_S;
		  while (sem->sleep != ENV_NULL) {
			   uint16_t next = envs[sem->sleep].sched_info.next;
			   
			   if (next == sem->sleep)
					next = ENV_NULL;
			   sem->sleep = next;
		  
			   sch_wakeup(next, NULL);
		  }
		  NOINTR_E;
	 }

	 if (count > 0)
		  sem->count = count;
	 sl_release(&sem->lock);
}
